package fr.ece.demo.threads1;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.math.BigInteger;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

/**
 * This Swing application calculates the factorial of an entered integer.
 * The application handles very large (nearly unlimited) integers by using the class BigInteger.
 * 
 * The application has a defect due to the lack of multi-threading. When you run a large calculation, 
 * the interface will freeze, as the factorial calc is performed by the Swing thread. As a result,
 * it is unable to handle other events (such as close windows, resize) or refresh the interface. Only
 * the final result is displayed.
 * 
 * A future solution will provide a multi-threading solution that overcomes this problem.
 * 
 * Note the internal architecture of the application, which separates the view from the 
 * model-controller. This is a common pattern which for the moment is overkill, but will make the
 * introduction of multi-threading much easier since the part of the program (the calculation)
 * that we wish to launch in a thread is clearly isolated from the view, which will execute in its
 * own thread (the Swing thread).
 *  
 * 
 * @author templemo
 *
 */
public class FactorialApp extends JFrame 
{
	private JLabel resultLabel;
	private JTextField valueField;
	private JButton goButton;
	private JLabel actionLabel;
	private FactorialLogic calculator;
	private FactorialApp that = this;					// workaround to allow inner classes to access "this" object
	
	/**
	 * A factorial application.
	 * Consists of a Window which accepts an integer and displays the factorial value, with
	 * intermediate values.
	 */
	public FactorialApp() 
	{
		// link to model (bidir, so model is NOT independant of view)
		this.calculator = new FactorialLogic(this);
		// set up view
		this.setTitle("SimpleFactorialApp");
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.buildGUI();
		this.setSize (500,100);						// choose a reasonable size
		this.setLocationRelativeTo(null);			// center on screen
		this.setVisible(true);						// make it visible - always last!
	}
	
	/**
	 * Builds the interface
	 */
	private void buildGUI ()
	{
		// panel to hold all and act as the content pane
		JPanel contentPane = new JPanel();
		contentPane.setLayout (new BorderLayout());
		
		// panel to hold the app controls
		StartFactorialController startEventHandler = new StartFactorialController();
		valueField = new JTextField();
		valueField.addActionListener(startEventHandler);		// handles pressing "Enter"
		goButton = new JButton ("Go!");
		goButton.addActionListener(startEventHandler);			// handles button press
		JPanel controlPanel = new JPanel();
		controlPanel.setLayout (new BorderLayout());
		controlPanel.add(valueField);
		controlPanel.add(goButton, BorderLayout.LINE_END);
		contentPane.add(controlPanel, BorderLayout.PAGE_START);
		
		// add labels to hold the intermediate display and final result
		JPanel resultPanel = new JPanel();
		resultPanel.setLayout(new BoxLayout(resultPanel, BoxLayout.Y_AXIS));
		actionLabel = new JLabel ("Inactive.");
		resultLabel = new JLabel("");
		actionLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
		resultLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
		resultPanel.add (actionLabel);
		resultPanel.add (resultLabel);
		contentPane.add (resultPanel);
		
		this.setContentPane(contentPane);
	}
	
	/*
	 * Here I use an inner class to handle the event. An inner class is defined INSIDE another class.
	 * The advantage is that, as a class it is modular, and as an inner class, it has access to the
	 * attributes of the enclosing class. Very useful if your event handler needs to update the
	 * enclosing class and is not used elsewhere outside the class. 
	 */
	class StartFactorialController implements ActionListener
	{
		public void actionPerformed(ActionEvent arg0) 
		{
			actionLabel.setText("Inactive.");
			resultLabel.setText("");
			try
			{
				/*
				 * You will notice that during a long calc, nothing seems to work anymore.
				 * This is because the Swing event thread is busy doing the calculation, and
				 * cannot at the same time respond to events, or refresh the display.
				 * For example, the window will not close (event not handled) and the button
				 * will not be disabled (display not refreshed).  This can be doubly nasty, as
				 * the events that Swing receives while it is busy are not discarded, but
				 * kept and handled afterwards (try for example clicking multiple times on the "Go" button
				 * during a calculation!). 
				 */
				goButton.setEnabled(false);							// disable the button during calc
				long value = Long.parseLong(valueField.getText());
				BigInteger result = calculator.calculate(value);
				actionLabel.setText("Final result:");
				resultLabel.setText(result.toString());
				goButton.setEnabled(true);							// reenable the button 
			}
			catch (NumberFormatException e)
			{
				JOptionPane.showMessageDialog(that, "Please enter a valid integer", "Number format error", JOptionPane.ERROR_MESSAGE);
			}
			valueField.selectAll();
			valueField.requestFocusInWindow();
		}
	}
	/**
	 * Displays an intermediate result received from the calculator.
	 * @param value
	 */
	public void displayIntermediateValue(BigInteger value) 
	{
		resultLabel.setText(value.toString());
	}
	/**
	 * Run the application!
	 * @param args
	 */
	public static void main(String[] args) 
	{
		new FactorialApp();
	}
}
